
#include <gfx/iindexbuffer.h>
#include <gfx/ivertexbuffer.h>
#include <gfx/ivertexdeclaration.h>
#include <gfx/mesh.h>
#include <utils/meshcreator.h>
#include <vector>
#include <stdio.h>

ceMeshCreator::ceMeshCreator ()
  : _device(0)
{
}

bool ceMeshCreator::Initialize (iDevice* device)
{
  if (!device)
		{
			return false;
		}

	_device = device;
	return true;
}

ceRef<iPatchMesh> ceMeshCreator::CreateTerrain(const ceVector3f&	min,
																					const ceVector3f&	max, 
																					unsigned					numVerticesP, 
																					unsigned					numVerticesQ, 
																					const float*			heights, 
																					unsigned					numPatchesP, 
																					unsigned					numPatchesQ, 
																					float							patchDistance, 
																					bool							invertP,
																					bool							invertQ,
																					bool							createTangents)
{
	if (createTangents)
		{
			printf ("Creating tangents for a terrain mesh isn't implemented yet: %s@%d\n", __FILE__, __LINE__);
			return 0;
		}

	unsigned vertexSize = 8;
	if (createTangents)
		{
			vertexSize += 3;
		}

	unsigned offset = 0;
	unsigned textureStage = 0;
	std::vector<ceVertexElement> elements;
  elements.push_back(ceVertexElement (ceProgramParameterName(PPB_Position0), DT_Float, 3, offset, vertexSize * sizeof(float), 0));
	offset += 3 * sizeof(float);
  elements.push_back(ceVertexElement (ceProgramParameterName(PPB_Normal0),	 DT_Float, 3, offset, vertexSize * sizeof(float), 0));
	offset += 3 * sizeof(float);
  elements.push_back(ceVertexElement (ceProgramParameterName(PPB_TexCoord0 + textureStage), DT_Float, 2, offset, vertexSize * sizeof(float), 0));
	offset += 2 * sizeof(float);
	textureStage++;
	if (createTangents)
		{
      elements.push_back(ceVertexElement (ceProgramParameterName(PPB_TexCoord0 + textureStage), DT_Float, 3, offset, vertexSize * sizeof(float), 0));
			offset += 3 * sizeof(float);
			textureStage++;
		}
	elements.push_back (ceVertexElement ());

	iVertexDeclaration* vd = _device->CreateVertexDeclaration(&elements[0]);

	ceVector3f diff = max - min;

  unsigned numVertexFloats = vertexSize * numVerticesP * numVerticesQ;
  float* vertexBuffer = new float [numVertexFloats];
  float* vptr = vertexBuffer;
  float tps = invertP ? -1.0f : 1.0f;
  float tqs = invertQ ? -1.0f : 1.0f;
#define HVALUE(p,q) (heights[q * numVerticesP + p])
  for (unsigned q=0; q<numVerticesQ; q++)
    {
      float fq = (float)q / (float)(numVerticesQ-1);
      unsigned rq = invertQ ? numVerticesQ - q - 1 : q;
      for (unsigned p=0; p<numVerticesP; p++)
        {
          float fp = (float)p / (float)(numVerticesP-1);

          unsigned rp = invertP ? numVerticesP - p - 1 : p;

          // the position
					*vptr++ = min.x + fp * diff.x;
          *vptr++ = -(min.y + fq * diff.y);
          *vptr++ = min.z + HVALUE(rp, rq) * diff.z;

          // handle normal calculation later.
          ceVector3f normal = CalcTerrainNormal(heights, p, q, numVerticesP, numVerticesQ, diff.x / (float)(numVerticesP-1), diff.y / (float)(numVerticesQ-1));
          *vptr++ = normal.x;
          *vptr++ = -normal.y;
          *vptr++ = normal.z;

          // texture is simple the factorial scaled up
          *vptr++ = fp * tps;
          *vptr++ = fq * tqs;

					if (createTangents)
            {
              // handle tangent calcualtion later.
              *vptr++ = 1.0f;
              *vptr++ = 0.0f;
              *vptr++ = 0.0f;
            }
        }
    }
#undef HVALUE

  SmoothTerrainNormals(vertexBuffer, 3, vertexSize, numVerticesP, numVerticesQ);

  iVertexBuffer* vb = _device->CreateVertexBuffer(numVertexFloats * sizeof(float), vertexBuffer, BDM_Static);
  delete [] vertexBuffer;


  iPatchMesh* patch = _device->CreatePatchMesh ();
	if (!patch->Initialize (_device, vb, numVerticesP, numVerticesQ, vd, numPatchesP, numPatchesQ, patchDistance, false, false))
		{
			delete patch;
			patch = 0;
		}
	return patch;

}

void ceMeshCreator::SmoothTerrainNormals(float *vertexData, unsigned offset, unsigned stride, unsigned numI, unsigned numJ)
{
  ceVector3f *newNormals = new ceVector3f [numI * numJ];
  unsigned ptr = 0;

  float influence = 0.75f;

#define PTR(pi, pj) ((pj) * numI + (pi)) * stride + offset
#define VEC(pi,pj,name) ptr = PTR(pi, pj); ceVector3f name (vertexData[ptr+0], vertexData[ptr+1], vertexData[ptr+2])

  //
  // First build the smoothed normals in a different vector so the new
  // created normals don't influence each other
  ceVector3f* nptr = newNormals;
  for (unsigned j=0; j<numJ; ++j)
    {
      for (unsigned i=0; i<numI; ++i)
        {
          ceVector3f normal (0.0f, 0.0f, 0.0f);
          VEC(i, j, vec);
          normal += vec;

          if (i > 0)
            {
              VEC(i-1, j, vec);
              normal += (vec * influence);
            }
          if (i < (numI-1))
            {
              VEC(i+1, j, vec);
              normal += (vec * influence);
            }

          if (j > 0)
            {
              VEC(i, j-1, vec);
              normal += (vec * influence);
            }
          if (j < (numJ-1))
            {
              VEC(i, j+1, vec);
              normal += (vec * influence);
            }

          *nptr++ = normal.Normalized();
        }
    }
#undef VEC

  //
  // now copy the newly calculated normals into the origin vertex buffer
  nptr = newNormals;
  for (unsigned j=0; j<numJ; ++j)
    {
      for (unsigned i=0; i<numI; ++i)
        {
          ptr = PTR(i, j);
          ceVector3f& n = *nptr++;
          vertexData[ptr+0] = n.x;
          vertexData[ptr+1] = n.y;
          vertexData[ptr+2] = n.z;
        }
    }

  // clean up
  delete [] newNormals;
}


ceVector3f ceMeshCreator::CalcTerrainNormal(const float *heights, unsigned i, unsigned j, unsigned numI, unsigned numJ, float distI, float distJ)
{
  ceVector3f normal = ceVector3f (0.0f, 0.0f, 0.0f);

#define HVALUE(p,q) (heights[(q) * numJ + (p)])

  ceVector3f center (0.0f, 0.0f, HVALUE(i, j));

  bool i0 = false, i1 = false, i2 = false, i3 = false;
  ceVector3f other[4];
  if (j > 0)
    {
      i0 = true;
      other[0] = ceVector3f (0.0f, -distJ, HVALUE(i, j-1));
      other[0] -= center;
      other[0].Normalize();
    }
  if (j < (numJ-1))
    {
      i2 = true;
      other[2] = ceVector3f (0.0f, distJ, HVALUE(i, j+1));
      other[2] -= center;
      other[2].Normalize();
    }
  if (i > 0)
    {
      i3 = true;
      other[3] = ceVector3f (-distI, 0.0f, HVALUE(i-1, j));
      other[3] -= center;
      other[3].Normalize();
    }
  if (i < (numI-1))
    {
      i1 = true;
      other[1] = ceVector3f (distI, 0.0f, HVALUE(i+1, j));
      other[1] -= center;
      other[1].Normalize();
    }

  if (i3 && i0)
    {
      normal += other[3] % other[0];
    }
  if (i0 && i1)
    {
      normal += other[0] % other[1];
    }
  if (i1 && i2)
    {
      normal += other[1] % other[2];
    }
  if (i2 && i3)
    {
      normal += other[2] % other[3];
    }
#undef HVALUE

  return normal.Normalized();
}


void ceMeshCreator::Clear()
{
  _vertices.clear();
  _indices.clear();
}

void ceMeshCreator::AddVertex(const ceVector3f &xyz, const ceVector3f &normal, const ceVector2f &uv)
{
  Vertex v;
  v.XYZ = xyz;
  v.Normal = normal;
  v.UV = uv;
  _vertices.push_back(v);
}

void ceMeshCreator::AddLine(unsigned i0, unsigned i1)
{
  _indices.push_back(i0);
  _indices.push_back(i1);
}

void ceMeshCreator::AddTrigon(unsigned i0, unsigned i1, unsigned i2)
{
  _indices.push_back(i0);
  _indices.push_back(i1);
  _indices.push_back(i2);
}

void ceMeshCreator::AddQuad(unsigned i0, unsigned i1, unsigned i2, unsigned i3)
{
  _indices.push_back(i0);
  _indices.push_back(i1);
  _indices.push_back(i2);

  _indices.push_back(i0);
  _indices.push_back(i2);
  _indices.push_back(i3);
}

ceRef<iStaticMesh> ceMeshCreator::CreateStaticMesh(cePrimitiveType primType)
{
  unsigned nv = _vertices.size();
  unsigned ni = _indices.size();
  if (!nv || !ni)
    {
      return 0;
    }
  if ((primType == PT_Triangles && (ni % 3) != 0) ||
      (primType == PT_Triangles && (ni % 2) != 0))
    {
      return 0;
    }

  iVertexBuffer* vb = 0;
  iIndexBuffer* ib = 0;

  ceMesh* mesh = new ceMesh();
  ceBoundingBox bbox;
  bbox.Clear();

  float *vertices = new float [nv * 8];
  float *vptr = vertices;
  for (unsigned i=0; i<nv; ++i)
    {
      const Vertex& vertex = _vertices[i];
      bbox.Add(vertex.XYZ);

      *vptr++ = vertex.XYZ.x;
      *vptr++ = vertex.XYZ.y;
      *vptr++ = vertex.XYZ.z;

      *vptr++ = vertex.Normal.x;
      *vptr++ = vertex.Normal.y;
      *vptr++ = vertex.Normal.z;

      *vptr++ = vertex.UV.x;
      *vptr++ = vertex.UV.y;
    }
  bbox.Update();

  vb = _device->CreateVertexBuffer(nv * 8 * sizeof(float), vertices, BDM_Static);
  delete [] vertices;

  if (nv >= 65536)
    {
      unsigned* indices = new unsigned [ni];
      unsigned* iptr = indices;
      for (unsigned i=0; i<ni; ++i)
        {
          *iptr++ = _indices[i];
        }
      ib = _device->CreateIndexBuffer(ni * sizeof(unsigned), indices, BDM_Static);
      delete [] indices;
      mesh->Set32BitIndices(true);
    }
  else
    {
      unsigned short* indices = new unsigned short[ni];
      unsigned short* iptr = indices;
      for (unsigned i=0; i<ni; ++i)
        {
          *iptr++ = (unsigned short)_indices[i];
        }
      ib = _device->CreateIndexBuffer(ni * sizeof(unsigned short), indices, BDM_Static);
      delete [] indices;
      mesh->Set32BitIndices(false);
    }

  static ceProgramParameterName positionName  (PPB_Position0);
  static ceProgramParameterName normalName    (PPB_Normal0);
  static ceProgramParameterName texCoord0Name (PPB_TexCoord0);

  ceVertexElement vertexElements []  =
    {
      ceVertexElement(positionName, DT_Float, 3, 0,  32, 0),
      ceVertexElement(normalName,   DT_Float, 3, 12, 32, 0),
      ceVertexElement(texCoord0Name, DT_Float, 2, 24, 32, 0),
      ceVertexElement()
    };
  iVertexDeclaration* vd = _device->CreateVertexDeclaration(vertexElements);

  mesh->SetPrimitiveType(primType);
  mesh->SetBoundingBox(bbox);
  mesh->SetVertices(vb);
  mesh->SetIndices(ib);
  mesh->SetNumberOfTrigons(ni / 3);
  mesh->SetVertexDeclaration(vd);

  CE_UNSET(vb);
  CE_UNSET(ib);
  CE_UNSET(vd);

  iStaticMesh* smesh = _device->CreateStaticMesh();
  smesh->SetMesh(mesh);

  return smesh;
}
